/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
import {Schema} from './schema';
import {
  argbFromHex,
  hexFromArgb,
  TonalPalette,
  Hct,
  DynamicScheme,
  DislikeAnalyzer,
  TemperatureCache,
} from '@material/material-color-utilities';

// For each color tonal palettes are created using the following hue tones. The
// tonal palettes then get used to create the different color roles (ex.
// on-primary) https://m3.material.io/styles/color/system/how-the-system-works
const HUE_TONES = [0, 10, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100];
// Map of neutral hues to the previous/next hues that
// can be used to estimate them, in case they're missing.
const NEUTRAL_HUES = new Map<number, {prev: number; next: number}>([
  [4, {prev: 0, next: 10}],
  [6, {prev: 0, next: 10}],
  [12, {prev: 10, next: 20}],
  [17, {prev: 10, next: 20}],
  [22, {prev: 20, next: 25}],
  [24, {prev: 20, next: 25}],
  [87, {prev: 80, next: 90}],
  [92, {prev: 90, next: 95}],
  [94, {prev: 90, next: 95}],
  [96, {prev: 95, next: 98}],
]);

// Note: Some of the color tokens refer to additional hue tones, but this only
// applies for the neutral color palette (ex. surface container is neutral
// palette's 94 tone). https://m3.material.io/styles/color/static/baseline
const NEUTRAL_HUE_TONES = [...HUE_TONES, ...NEUTRAL_HUES.keys()];

export interface ColorPalettes {
  primary: TonalPalette;
  secondary: TonalPalette;
  tertiary: TonalPalette;
  neutral: TonalPalette;
  neutralVariant: TonalPalette;
  error: TonalPalette;
}

/**
 * Gets Hct representation of Hex color.
 * @param color Hex color.
 * @returns Hct color.
 */
export function getHctFromHex(color: string): Hct {
  try {
    return Hct.fromInt(argbFromHex(color));
  } catch (e) {
    throw new Error(
      'Cannot parse the specified color ' +
        color +
        '. Please verify it is a hex color (ex. #ffffff or ffffff).',
    );
  }
}

/**
 * Gets color tonal palettes generated by Material from the provided color.
 * @param primaryPalette Tonal palette that represents primary.
 * @param secondaryPalette Tonal palette that represents secondary.
 * @param tertiaryPalette Tonal palette that represents tertiary.
 * @param neutralPalette Tonal palette that represents neutral.
 * @param neutralVariantPalette Tonal palette that represents neutral variant.
 * @param isDark Boolean to represent if the scheme is for a dark or light theme.
 * @param contrastLevel Number between -1 and 1 for the contrast level. 0 is the standard contrast
 * and 1 represents high contrast.
 * @returns Dynamic scheme for provided theme and contrast level
 */
export function getMaterialDynamicScheme(
  primaryPalette: TonalPalette,
  secondaryPalette: TonalPalette,
  tertiaryPalette: TonalPalette,
  neutralPalette: TonalPalette,
  neutralVariantPalette: TonalPalette,
  isDark: boolean,
  contrastLevel: number,
): DynamicScheme {
  return new DynamicScheme({
    sourceColorArgb: primaryPalette.keyColor.toInt(),
    variant: 6, // Variant.FIDELITY, used number representation since enum is not accessible outside of @material/material-color-utilities
    contrastLevel: contrastLevel,
    isDark: isDark,
    primaryPalette: primaryPalette,
    secondaryPalette: secondaryPalette,
    tertiaryPalette: tertiaryPalette,
    neutralPalette: neutralPalette,
    neutralVariantPalette: neutralVariantPalette,
  });
}

/**
 * Gets all the color palettes.
 *
 * @param primaryColor Hex color that represents the primary palette.
 * @param secondaryColor Hex color that represents the secondary palette.
 * @param tertiaryColor Hex color that represents the tertiary palette.
 * @param neutralColor Hex color that represents the neutral palette.
 * @returns Object with all the M3 color palettes
 */
export function getColorPalettes(
  primaryColor: string,
  secondaryColor?: string,
  tertiaryColor?: string,
  neutralColor?: string,
  neutralVariantColor?: string,
  errorColor?: string,
): ColorPalettes {
  // Create tonal palettes for each color and custom color overrides if applicable. Used for both
  // standard contrast and high contrast schemes since they share the same tonal palettes.
  // The math to generate the palettes follows how palettes are generated for SchemeFidelity
  // (https://github.com/material-foundation/material-color-utilities/blob/main/typescript/scheme/scheme_fidelity.ts).
  // Cannot create object directly since we allow users to enter custom colors for palettes and
  // palettes are readonly for a DynamicScheme.
  const primaryColorHct = getHctFromHex(primaryColor);
  const primaryPalette = TonalPalette.fromHct(primaryColorHct);

  let secondaryPalette;
  if (secondaryColor) {
    secondaryPalette = TonalPalette.fromHct(getHctFromHex(secondaryColor));
  } else {
    secondaryPalette = TonalPalette.fromHueAndChroma(
      primaryColorHct.hue,
      Math.max(primaryColorHct.chroma - 32.0, primaryColorHct.chroma * 0.5),
    );
  }

  let tertiaryPalette;
  if (tertiaryColor) {
    tertiaryPalette = TonalPalette.fromHct(getHctFromHex(tertiaryColor));
  } else {
    tertiaryPalette = TonalPalette.fromInt(
      DislikeAnalyzer.fixIfDisliked(
        new TemperatureCache(primaryColorHct).analogous(3, 6)[2],
      ).toInt(),
    );
  }

  let neutralPalette;
  if (neutralColor) {
    neutralPalette = TonalPalette.fromHct(getHctFromHex(neutralColor));
  } else {
    neutralPalette = TonalPalette.fromHueAndChroma(
      primaryColorHct.hue,
      primaryColorHct.chroma / 8.0,
    );
  }

  let neutralVariantPalette;
  if (neutralVariantColor) {
    neutralVariantPalette = TonalPalette.fromHct(getHctFromHex(neutralVariantColor));
  } else {
    neutralVariantPalette = TonalPalette.fromHueAndChroma(
      primaryColorHct.hue,
      primaryColorHct.chroma / 8.0 + 4.0,
    );
  }

  let errorPalette;
  if (errorColor) {
    errorPalette = TonalPalette.fromHct(getHctFromHex(errorColor));
  } else {
    // Need to create color scheme to get generated error tonal palette.
    errorPalette = getMaterialDynamicScheme(
      primaryPalette,
      secondaryPalette,
      tertiaryPalette,
      neutralPalette,
      neutralVariantPalette,
      /* isDark */ false,
      /* contrastLevel */ 0,
    ).errorPalette;
  }

  return {
    primary: primaryPalette,
    secondary: secondaryPalette,
    tertiary: tertiaryPalette,
    neutral: neutralPalette,
    neutralVariant: neutralVariantPalette,
    error: errorPalette,
  };
}

/**
 * Gets the scss representation of the provided color palettes.
 * @param colorPalettes Object with all the M3 color palettes.
 * @returns String of the color palettes scss.
 */
function getColorPalettesSCSS(colorPalettes: ColorPalettes): string {
  let scss = '(\n';
  for (const [variant, palette] of Object.entries(colorPalettes)) {
    const paletteKey = variant.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
    scss += '  ' + paletteKey + ': (\n';
    const tones = paletteKey === 'neutral' ? NEUTRAL_HUE_TONES : HUE_TONES;
    for (const tone of tones) {
      const color = hexFromArgb(palette.tone(tone));
      scss += '    ' + tone + ': ' + color + ',\n';
    }
    scss += '  ),\n';
  }
  scss += ');';
  return scss;
}

/**
 * Gets the generated scss from the provided color palettes and theme types.
 * @param colorPalettes Object with all the M3 color palettes.
 * @param colorComment Comment with original hex colors used to generate palettes.
 * @returns String of the generated theme scss.
 */
export function generateSCSSTheme(colorPalettes: ColorPalettes, colorComment: string): string {
  let scss = [
    "// This file was generated by running 'ng generate @angular/material:theme-color'.",
    '// Proceed with caution if making changes to this file.',
    '',
    "@use 'sass:map';",
    "@use '@angular/material' as mat;",
    '',
    '// Note: ' + colorComment,
    '$_palettes: ' + getColorPalettesSCSS(colorPalettes),
    '',
    '$_rest: (',
    '  secondary: map.get($_palettes, secondary),',
    '  neutral: map.get($_palettes, neutral),',
    '  neutral-variant: map.get($_palettes,  neutral-variant),',
    '  error: map.get($_palettes, error),',
    ');',
    '',
    '$primary-palette: map.merge(map.get($_palettes, primary), $_rest);',
    '$tertiary-palette: map.merge(map.get($_palettes, tertiary), $_rest);',
  ];

  return scss.join('\n');
}

/**
 * Gets map of system variables and their high contrast values.
 * @param colorScheme Dynamic material scheme for a high contrast theme which contains the color role values.
 * @returns Map of system variables names and their high contrast values.
 */
function getHighContrastOverides(colorScheme: DynamicScheme): Map<string, string> {
  const overrides = new Map<string, string>();

  // Set system variables with values from primary palette
  overrides.set('primary', hexFromArgb(colorScheme.primary));
  overrides.set('on-primary', hexFromArgb(colorScheme.onPrimary));
  overrides.set('primary-container', hexFromArgb(colorScheme.primaryContainer));
  overrides.set('on-primary-container', hexFromArgb(colorScheme.onPrimaryContainer));
  overrides.set('inverse-primary', hexFromArgb(colorScheme.inversePrimary));
  overrides.set('primary-fixed', hexFromArgb(colorScheme.primaryFixed));
  overrides.set('primary-fixed-dim', hexFromArgb(colorScheme.primaryFixedDim));
  overrides.set('on-primary-fixed', hexFromArgb(colorScheme.onPrimaryFixed));
  overrides.set('on-primary-fixed-variant', hexFromArgb(colorScheme.onPrimaryFixedVariant));

  // Set system variables with values from secondary palette
  overrides.set('secondary', hexFromArgb(colorScheme.secondary));
  overrides.set('on-secondary', hexFromArgb(colorScheme.onSecondary));
  overrides.set('secondary-container', hexFromArgb(colorScheme.secondaryContainer));
  overrides.set('on-secondary-container', hexFromArgb(colorScheme.onSecondaryContainer));
  overrides.set('secondary-fixed', hexFromArgb(colorScheme.secondaryFixed));
  overrides.set('secondary-fixed-dim', hexFromArgb(colorScheme.secondaryFixedDim));
  overrides.set('on-secondary-fixed', hexFromArgb(colorScheme.onSecondaryFixed));
  overrides.set('on-secondary-fixed-variant', hexFromArgb(colorScheme.onSecondaryFixedVariant));

  // Set system variables with values from tertiary palette
  overrides.set('tertiary', hexFromArgb(colorScheme.tertiary));
  overrides.set('on-tertiary', hexFromArgb(colorScheme.onTertiary));
  overrides.set('tertiary-container', hexFromArgb(colorScheme.tertiaryContainer));
  overrides.set('on-tertiary-container', hexFromArgb(colorScheme.onTertiaryContainer));
  overrides.set('tertiary-fixed', hexFromArgb(colorScheme.tertiaryFixed));
  overrides.set('tertiary-fixed-dim', hexFromArgb(colorScheme.tertiaryFixedDim));
  overrides.set('on-tertiary-fixed', hexFromArgb(colorScheme.onTertiaryFixed));
  overrides.set('on-tertiary-fixed-variant', hexFromArgb(colorScheme.onTertiaryFixedVariant));

  // Set system variables with values from neutral palette
  overrides.set('background', hexFromArgb(colorScheme.background));
  overrides.set('on-background', hexFromArgb(colorScheme.onBackground));
  overrides.set('surface', hexFromArgb(colorScheme.surface));
  overrides.set('surface-dim', hexFromArgb(colorScheme.surfaceDim));
  overrides.set('surface-bright', hexFromArgb(colorScheme.surfaceBright));
  overrides.set('surface-container-low', hexFromArgb(colorScheme.surfaceContainerLow));
  overrides.set('surface-container-lowest', hexFromArgb(colorScheme.surfaceContainerLowest));
  overrides.set('surface-container', hexFromArgb(colorScheme.surfaceContainer));
  overrides.set('surface-container-high', hexFromArgb(colorScheme.surfaceContainerHigh));
  overrides.set('surface-container-highest', hexFromArgb(colorScheme.surfaceContainerHighest));
  overrides.set('on-surface', hexFromArgb(colorScheme.onSurface));
  overrides.set('shadow', hexFromArgb(colorScheme.shadow));
  overrides.set('scrim', hexFromArgb(colorScheme.scrim));
  overrides.set('surface-tint', hexFromArgb(colorScheme.surfaceTint));
  overrides.set('inverse-surface', hexFromArgb(colorScheme.inverseSurface));
  overrides.set('inverse-on-surface', hexFromArgb(colorScheme.inverseOnSurface));
  overrides.set('outline', hexFromArgb(colorScheme.outline));
  overrides.set('outline-variant', hexFromArgb(colorScheme.outlineVariant));

  // Set system variables with values from error palette
  overrides.set('error', hexFromArgb(colorScheme.error));
  overrides.set('on-error', hexFromArgb(colorScheme.onError));
  overrides.set('error-container', hexFromArgb(colorScheme.errorContainer));
  overrides.set('on-error-container', hexFromArgb(colorScheme.onErrorContainer));

  // Set system variables with values from neutral variant palette
  overrides.set('surface-variant', hexFromArgb(colorScheme.surfaceVariant));
  overrides.set('on-surface-variant', hexFromArgb(colorScheme.onSurfaceVariant));

  return overrides;
}

/**
 * Gets the scss representation of the high contrast override mixins.
 * @param lightHighContrastColorScheme Dynamic material scheme for light high contrast themes which contains the color role values.
 * @param darkHighContrastColorScheme Dynamic material scheme for dark high contrast themes which contains the color role values.
 * @returns String of the generated high contrast mixins scss.
 */
function generateHighContrastOverrideMixinsSCSS(
  lightHighContrastColorScheme: DynamicScheme,
  darkHighContrastColorScheme: DynamicScheme,
): string {
  // Create private function to grab correct values based on theme-type
  let scss = '\n';
  scss += '\n@function _high-contrast-value($light, $dark, $theme-type) {\n';
  scss += '  @if ($theme-type == light) {\n';
  scss += '    @return $light;\n';
  scss += '  }\n';
  scss += '  @if ($theme-type == dark) {\n';
  scss += '    @return $dark;\n';
  scss += '  }\n';
  scss += '  @if ($theme-type == color-scheme) {\n';
  scss += '    @return light-dark(#{$light}, #{$dark});\n';
  scss += '  }\n';
  scss +=
    "  \n  @error 'Unknown theme-type #{$theme-type}. Expected light, dark, or color-scheme';\n";
  scss += '}\n';

  // Populate maps with color roles values for light and dark themes to pass into mixin
  const lightOverrides = getHighContrastOverides(lightHighContrastColorScheme);
  const darkOverrides = getHighContrastOverides(darkHighContrastColorScheme);

  // Create high contrast mixin with theme-type input that can be light, dark, or color-scheme.
  scss += '\n@mixin high-contrast-overrides($theme-type) {\n';
  scss += '  @include mat.theme-overrides((\n';
  for (const [key, value] of lightOverrides!.entries()) {
    scss +=
      '    ' +
      key +
      ': _high-contrast-value(' +
      value +
      ', ' +
      darkOverrides.get(key) +
      ', $theme-type),\n';
  }
  scss += '  ))\n';
  scss += ' }\n';

  return scss;
}

/**
 * Gets CSS to define a system variable with light dark values.
 * @param leftSpacing Amount of spaces to add before the variable name.
 * @param variableName System level variable name (ex. on-primary).
 * @param lightColor Hex color for light theme.
 * @param darkColor Hex color for dark theme.
 * @param comment Optional comment to after the variable definition on the same line.
 * @returns String with system level variable definition.
 */
function createLightDarkVar(
  leftSpacing: string,
  variableName: string,
  lightColor: number,
  darkColor: number,
  comment?: string,
) {
  const commentContent = comment ? ' /* ' + comment + ' */' : '';
  const lightDarkValue =
    'light-dark(' + hexFromArgb(lightColor) + ', ' + hexFromArgb(darkColor) + ');';
  return leftSpacing + '--mat-sys-' + variableName + ': ' + lightDarkValue + commentContent + '\n';
}

/**
 * Gets CSS for color system variables.
 * @param lightScheme Dynamic material scheme for a light theme which contains the color role values.
 * @param darkScheme Dynamic material scheme for a dark theme which contains the color role values.
 * @param isHighContrast Boolean to represent if color variables are high contrast or not.
 * @returns String with CSS for color system variables.
 */
function getColorSysVariablesCSS(
  lightScheme: DynamicScheme,
  darkScheme: DynamicScheme,
  isHighContrast: boolean = false,
): string {
  let css = '';

  // Add extra spacing for high contrast values since variables will be nested within a media query.
  let leftSpacing = ' '.repeat(isHighContrast ? 4 : 2);

  // Set system variables with values from primary palette
  css += leftSpacing + '/* Primary palette variables */\n';
  // For certain color variables the value is grabbed directly from the palette rather than the role
  // for the non high contrast values. The color roles keeps track of color tone relationships
  // (ex. primary and primary container) and can slightly change the colors. Grabbing the value
  // directly from the palette allows the colors to be consistent with how system color variables
  // are defined in scss.
  css += createLightDarkVar(
    leftSpacing,
    'primary',
    isHighContrast ? lightScheme.primary : lightScheme.primaryPalette.tone(40),
    isHighContrast ? darkScheme.primary : lightScheme.primaryPalette.tone(80),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-primary',
    isHighContrast ? lightScheme.onPrimary : lightScheme.primaryPalette.tone(100),
    isHighContrast ? darkScheme.onPrimary : darkScheme.primaryPalette.tone(20),
  );
  css += createLightDarkVar(
    leftSpacing,
    'primary-container',
    isHighContrast ? lightScheme.primaryContainer : lightScheme.primaryPalette.tone(90),
    isHighContrast ? darkScheme.primaryContainer : darkScheme.primaryPalette.tone(30),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-primary-container',
    isHighContrast ? lightScheme.onPrimaryContainer : lightScheme.primaryPalette.tone(10),
    isHighContrast ? darkScheme.onPrimaryContainer : darkScheme.primaryPalette.tone(90),
  );
  css += createLightDarkVar(
    leftSpacing,
    'inverse-primary',
    lightScheme.inversePrimary,
    darkScheme.inversePrimary,
  );
  css += createLightDarkVar(
    leftSpacing,
    'primary-fixed',
    isHighContrast ? lightScheme.primaryFixed : lightScheme.primaryPalette.tone(90),
    isHighContrast ? darkScheme.primaryFixed : darkScheme.primaryPalette.tone(90),
  );
  css += createLightDarkVar(
    leftSpacing,
    'primary-fixed-dim',
    isHighContrast ? lightScheme.primaryFixedDim : lightScheme.primaryPalette.tone(80),
    isHighContrast ? darkScheme.primaryFixedDim : darkScheme.primaryPalette.tone(80),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-primary-fixed',
    lightScheme.onPrimaryFixed,
    darkScheme.onPrimaryFixed,
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-primary-fixed-variant',
    lightScheme.onPrimaryFixedVariant,
    darkScheme.onPrimaryFixedVariant,
  );

  // Set system variables with values from secondary palette
  css += '\n' + leftSpacing + '/* Secondary palette variables */\n';
  css += createLightDarkVar(
    leftSpacing,
    'secondary',
    isHighContrast ? lightScheme.secondary : lightScheme.secondaryPalette.tone(40),
    isHighContrast ? darkScheme.secondary : darkScheme.secondaryPalette.tone(80),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-secondary',
    isHighContrast ? lightScheme.onSecondary : lightScheme.secondaryPalette.tone(100),
    isHighContrast ? darkScheme.onSecondary : darkScheme.secondaryPalette.tone(20),
  );
  css += createLightDarkVar(
    leftSpacing,
    'secondary-container',
    isHighContrast ? lightScheme.secondaryContainer : lightScheme.secondaryPalette.tone(90),
    isHighContrast ? darkScheme.secondaryContainer : darkScheme.secondaryPalette.tone(30),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-secondary-container',
    isHighContrast ? lightScheme.onSecondaryContainer : lightScheme.secondaryPalette.tone(10),
    isHighContrast ? darkScheme.onSecondaryContainer : darkScheme.secondaryPalette.tone(90),
  );
  css += createLightDarkVar(
    leftSpacing,
    'secondary-fixed',
    isHighContrast ? lightScheme.secondaryFixed : lightScheme.secondaryPalette.tone(90),
    isHighContrast ? darkScheme.secondaryFixed : darkScheme.secondaryPalette.tone(90),
  );
  css += createLightDarkVar(
    leftSpacing,
    'secondary-fixed-dim',
    isHighContrast ? lightScheme.secondaryFixedDim : lightScheme.secondaryPalette.tone(80),
    isHighContrast ? darkScheme.secondaryFixedDim : darkScheme.secondaryPalette.tone(80),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-secondary-fixed',
    lightScheme.onSecondaryFixed,
    darkScheme.onSecondaryFixed,
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-secondary-fixed-variant',
    lightScheme.onSecondaryFixedVariant,
    darkScheme.onSecondaryFixedVariant,
  );

  // Set system variables with values from tertiary palette
  css += '\n' + leftSpacing + '/* Tertiary palette variables */\n';
  css += createLightDarkVar(
    leftSpacing,
    'tertiary',
    isHighContrast ? lightScheme.tertiary : lightScheme.tertiaryPalette.tone(40),
    isHighContrast ? darkScheme.tertiary : darkScheme.tertiaryPalette.tone(80),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-tertiary',
    isHighContrast ? lightScheme.onTertiary : lightScheme.tertiaryPalette.tone(100),
    isHighContrast ? darkScheme.onTertiary : darkScheme.tertiaryPalette.tone(20),
  );
  css += createLightDarkVar(
    leftSpacing,
    'tertiary-container',
    isHighContrast ? lightScheme.tertiaryContainer : lightScheme.tertiaryPalette.tone(90),
    isHighContrast ? darkScheme.tertiaryContainer : darkScheme.tertiaryPalette.tone(30),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-tertiary-container',
    isHighContrast ? lightScheme.onTertiaryContainer : lightScheme.tertiaryPalette.tone(10),
    isHighContrast ? darkScheme.onTertiaryContainer : darkScheme.tertiaryPalette.tone(90),
  );
  css += createLightDarkVar(
    leftSpacing,
    'tertiary-fixed',
    isHighContrast ? lightScheme.tertiaryFixed : lightScheme.tertiaryPalette.tone(90),
    isHighContrast ? darkScheme.tertiaryFixed : darkScheme.tertiaryPalette.tone(90),
  );
  css += createLightDarkVar(
    leftSpacing,
    'tertiary-fixed-dim',
    isHighContrast ? lightScheme.tertiaryFixedDim : lightScheme.tertiaryPalette.tone(80),
    isHighContrast ? darkScheme.tertiaryFixedDim : darkScheme.tertiaryPalette.tone(80),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-tertiary-fixed',
    lightScheme.onTertiaryFixed,
    darkScheme.onTertiaryFixed,
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-tertiary-fixed-variant',
    lightScheme.onTertiaryFixedVariant,
    darkScheme.onTertiaryFixedVariant,
  );

  // Set system variables with values from neutral palette
  css += '\n' + leftSpacing + '/* Neutral palette variables */\n';
  css += createLightDarkVar(
    leftSpacing,
    'background',
    lightScheme.background,
    darkScheme.background,
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-background',
    lightScheme.onBackground,
    darkScheme.onBackground,
  );
  css += createLightDarkVar(leftSpacing, 'surface', lightScheme.surface, darkScheme.surface);
  css += createLightDarkVar(
    leftSpacing,
    'surface-dim',
    lightScheme.surfaceDim,
    darkScheme.surfaceDim,
  );
  css += createLightDarkVar(
    leftSpacing,
    'surface-bright',
    lightScheme.surfaceBright,
    darkScheme.surfaceBright,
  );
  css += createLightDarkVar(
    leftSpacing,
    'surface-container-low',
    lightScheme.surfaceContainerLow,
    darkScheme.surfaceContainerLow,
  );
  css += createLightDarkVar(
    leftSpacing,
    'surface-container-lowest',
    lightScheme.surfaceContainerLowest,
    darkScheme.surfaceContainerLowest,
  );
  css += createLightDarkVar(
    leftSpacing,
    'surface-container',
    lightScheme.surfaceContainer,
    darkScheme.surfaceContainer,
  );
  css += createLightDarkVar(
    leftSpacing,
    'surface-container-high',
    lightScheme.surfaceContainerHigh,
    darkScheme.surfaceContainerHigh,
  );
  css += createLightDarkVar(
    leftSpacing,
    'surface-container-highest',
    lightScheme.surfaceContainerHighest,
    darkScheme.surfaceContainerHighest,
  );
  css += createLightDarkVar(leftSpacing, 'on-surface', lightScheme.onSurface, darkScheme.onSurface);
  css += createLightDarkVar(leftSpacing, 'shadow', lightScheme.shadow, darkScheme.shadow);
  css += createLightDarkVar(leftSpacing, 'scrim', lightScheme.scrim, darkScheme.scrim);
  css += createLightDarkVar(
    leftSpacing,
    'surface-tint',
    lightScheme.surfaceTint,
    darkScheme.surfaceTint,
  );
  css += createLightDarkVar(
    leftSpacing,
    'inverse-surface',
    lightScheme.inverseSurface,
    darkScheme.inverseSurface,
  );
  css += createLightDarkVar(
    leftSpacing,
    'inverse-on-surface',
    lightScheme.inverseOnSurface,
    darkScheme.inverseOnSurface,
  );
  css += createLightDarkVar(leftSpacing, 'outline', lightScheme.outline, darkScheme.outline);
  css += createLightDarkVar(
    leftSpacing,
    'outline-variant',
    lightScheme.outlineVariant,
    darkScheme.outlineVariant,
  );
  css += createLightDarkVar(
    leftSpacing,
    'neutral10',
    lightScheme.neutralPalette.tone(10),
    darkScheme.neutralPalette.tone(10),
    'Variable used for the form field native select option text color',
  );

  // Set system variables with values from error palette
  css += '\n' + leftSpacing + '/* Error palette variables */\n';
  css += createLightDarkVar(
    leftSpacing,
    'error',
    isHighContrast ? lightScheme.error : lightScheme.errorPalette.tone(40),
    isHighContrast ? darkScheme.error : darkScheme.errorPalette.tone(80),
  );
  css += createLightDarkVar(leftSpacing, 'on-error', lightScheme.onError, darkScheme.onError);
  css += createLightDarkVar(
    leftSpacing,
    'error-container',
    isHighContrast ? lightScheme.errorContainer : lightScheme.errorPalette.tone(90),
    isHighContrast ? darkScheme.errorContainer : darkScheme.errorPalette.tone(30),
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-error-container',
    isHighContrast ? lightScheme.onErrorContainer : lightScheme.errorPalette.tone(10),
    isHighContrast ? darkScheme.onErrorContainer : darkScheme.errorPalette.tone(90),
  );

  // Set system variables with values from neutral variant palette
  css += '\n' + leftSpacing + '/* Neutral variant palette variables */\n';
  css += createLightDarkVar(
    leftSpacing,
    'surface-variant',
    lightScheme.surfaceVariant,
    darkScheme.surfaceVariant,
  );
  css += createLightDarkVar(
    leftSpacing,
    'on-surface-variant',
    lightScheme.onSurfaceVariant,
    darkScheme.onSurfaceVariant,
  );
  css += createLightDarkVar(
    leftSpacing,
    'neutral-variant20',
    lightScheme.neutralVariantPalette.tone(20),
    darkScheme.neutralVariantPalette.tone(20),
    'Variable used for the sidenav scrim (container background shadow when opened)',
  );

  return css;
}

/**
 * Gets CSS for typography system variables.
 * @returns String with CSS for typography system variables.
 */
function getTypographySysVariablesCSS(): string {
  let css = '';

  // Define typography variables to be used in the different typeface system variables
  css += '\n  /* Typography variables. Only used in the different typescale system variables. */\n';
  css += '  --mat-sys-brand-font-family: Roboto; /* The font-family to use for brand text. */\n';
  css += '  --mat-sys-plain-font-family: Roboto; /* The font-family to use for plain text. */\n';
  css += '  --mat-sys-bold-font-weight: 700; /* The font-weight to use for bold text. */\n';
  css += '  --mat-sys-medium-font-weight: 500; /* The font-weight to use for medium text. */\n';
  css += '  --mat-sys-regular-font-weight: 400; /* The font-weight to use for regular text. */\n\n';

  css += '  /* Typescale variables. */\n';
  css +=
    '  /* Warning: Risk of reduced fidelity from using the composite typography tokens (ex. --mat-sys-body-large) since\n';
  css +=
    '     tracking cannot be represented in the "font" property shorthand. Consider using the discrete properties instead. */\n';

  // Body large typescale variables
  css +=
    '  --mat-sys-body-large: var(--mat-sys-body-large-weight) var(--mat-sys-body-large-size) / var(--mat-sys-body-large-line-height) var(--mat-sys-body-large-font);\n';
  css += '  --mat-sys-body-large-font: var(--mat-sys-plain-font-family);\n';
  css += '  --mat-sys-body-large-line-height: 1.5rem;\n';
  css += '  --mat-sys-body-large-size: 1rem;\n';
  css += '  --mat-sys-body-large-tracking: 0.031rem;\n';
  css += '  --mat-sys-body-large-weight: var(--mat-sys-regular-font-weight);\n';

  // Body medium typescale system variables
  css += '\n  /* Body medium typescale */\n';
  css +=
    '  --mat-sys-body-medium: var(--mat-sys-body-medium-weight) var(--mat-sys-body-medium-size) / var(--mat-sys-body-medium-line-height) var(--mat-sys-body-medium-font);\n';
  css += '  --mat-sys-body-medium-font: var(--mat-sys-plain-font-family);\n';
  css += '  --mat-sys-body-medium-line-height: 1.25rem;\n';
  css += '  --mat-sys-body-medium-size: 0.875rem;\n';
  css += '  --mat-sys-body-medium-tracking: 0.016rem;\n';
  css += '  --mat-sys-body-medium-weight: var(--mat-sys-regular-font-weight);\n';

  // Body small typescale system variables
  css += '\n  /* Body small typescale */\n';
  css +=
    '  --mat-sys-body-small: var(--mat-sys-body-small-weight) var(--mat-sys-body-small-size) / var(--mat-sys-body-small-line-height) var(--mat-sys-body-small-font);\n';
  css += '  --mat-sys-body-small-font: var(--mat-sys-plain-font-family);\n';
  css += '  --mat-sys-body-small-line-height: 1rem;\n';
  css += '  --mat-sys-body-small-size: 0.75rem;\n';
  css += '  --mat-sys-body-small-tracking: 0.025rem;\n';
  css += '  --mat-sys-body-small-weight: var(--mat-sys-regular-font-weight);\n';

  // Display large typescale system variables
  css += '\n  /* Display large typescale */\n';
  css +=
    '  --mat-sys-display-large: var(--mat-sys-display-large-weight) var(--mat-sys-display-large-size) / var(--mat-sys-display-large-line-height) var(--mat-sys-display-large-font);\n';
  css += '  --mat-sys-display-large-font: var(--mat-sys-brand-font-family);\n';
  css += '  --mat-sys-display-large-line-height: 4rem;\n';
  css += '  --mat-sys-display-large-size: 3.562rem;\n';
  css += '  --mat-sys-display-large-tracking: -0.016rem;\n';
  css += '  --mat-sys-display-large-weight: var(--mat-sys-regular-font-weight);\n';

  // Display medium typescale system variables
  css += '\n  /* Display medium typescale */\n';
  css +=
    '  --mat-sys-display-medium: var(--mat-sys-display-medium-weight) var(--mat-sys-display-medium-size) / var(--mat-sys-display-medium-line-height) var(--mat-sys-display-medium-font);\n';
  css += '  --mat-sys-display-medium-font: var(--mat-sys-brand-font-family);\n';
  css += '  --mat-sys-display-medium-line-height: 3.25rem;\n';
  css += '  --mat-sys-display-medium-size: 2.812rem;\n';
  css += '  --mat-sys-display-medium-tracking: 0;\n';
  css += '  --mat-sys-display-medium-weight: var(--mat-sys-regular-font-weight);\n';

  // Display small typescale system variables
  css += '\n  /* Display small typescale */\n';
  css +=
    '  --mat-sys-display-small: var(--mat-sys-display-small-weight) var(--mat-sys-display-small-size) / var(--mat-sys-display-small-line-height) var(--mat-sys-display-small-font);\n';
  css += '  --mat-sys-display-small-font: var(--mat-sys-brand-font-family);\n';
  css += '  --mat-sys-display-small-line-height: 2.75rem;\n';
  css += '  --mat-sys-display-small-size: 2.25rem;\n';
  css += '  --mat-sys-display-small-tracking: 0;\n';
  css += '  --mat-sys-display-small-weight: var(--mat-sys-regular-font-weight);\n';

  // Headline large typescale system variables
  css += '\n  /* Headline large typescale */\n';
  css +=
    '  --mat-sys-headline-large: var(--mat-sys-headline-large-weight) var(--mat-sys-headline-large-size) / var(--mat-sys-headline-large-line-height) var(--mat-sys-headline-large-font);\n';
  css += '  --mat-sys-headline-large-font: var(--mat-sys-brand-font-family);\n';
  css += '  --mat-sys-headline-large-line-height: 2.5rem;\n';
  css += '  --mat-sys-headline-large-size: 2rem;\n';
  css += '  --mat-sys-headline-large-tracking: 0;\n';
  css += '  --mat-sys-headline-large-weight: var(--mat-sys-regular-font-weight);\n';

  // Headline medium typescale system variables
  css += '\n  /* Headline medium typescale */\n';
  css +=
    '  --mat-sys-headline-medium: var(--mat-sys-headline-medium-weight) var(--mat-sys-headline-medium-size) / var(--mat-sys-headline-medium-line-height) var(--mat-sys-headline-medium-font);\n';
  css += '  --mat-sys-headline-medium-font: var(--mat-sys-brand-font-family);\n';
  css += '  --mat-sys-headline-medium-line-height: 2.25rem;\n';
  css += '  --mat-sys-headline-medium-size: 1.75rem;\n';
  css += '  --mat-sys-headline-medium-tracking: 0;\n';
  css += '  --mat-sys-headline-medium-weight: var(--mat-sys-regular-font-weight);\n';

  // Headline small typescale system variables
  css += '\n  /* Headline small typescale */\n';
  css +=
    '  --mat-sys-headline-small: var(--mat-sys-headline-small-weight) var(--mat-sys-headline-small-size) / var(--mat-sys-headline-small-line-height) var(--mat-sys-headline-small-font);\n';
  css += '  --mat-sys-headline-small-font: var(--mat-sys-brand-font-family);\n';
  css += '  --mat-sys-headline-small-line-height: 2rem;\n';
  css += '  --mat-sys-headline-small-size: 1.5rem;\n';
  css += '  --mat-sys-headline-small-tracking: 0;\n';
  css += '  --mat-sys-headline-small-weight: var(--mat-sys-regular-font-weight);\n';

  // Label large typescale system variables
  css += '\n  /* Label large typescale */\n';
  css +=
    '  --mat-sys-label-large: var(--mat-sys-label-large-weight) var(--mat-sys-label-large-size) / var(--mat-sys-label-large-line-height) var(--mat-sys-label-large-font);\n';
  css += '  --mat-sys-label-large-font: var(--mat-sys-plain-font-family);\n';
  css += '  --mat-sys-label-large-line-height: 1.25rem;\n';
  css += '  --mat-sys-label-large-size: 0.875rem;\n';
  css += '  --mat-sys-label-large-tracking: 0.006rem;\n';
  css += '  --mat-sys-label-large-weight: var(--mat-sys-medium-font-weight);\n';
  css += '  --mat-sys-label-large-weight-prominent: var(--mat-sys-bold-font-weight);\n';

  // Label medium typescale system variables
  css += '\n  /* Label medium typescale */\n';
  css +=
    '  --mat-sys-label-medium: var(--mat-sys-label-medium-weight) var(--mat-sys-label-medium-size) / var(--mat-sys-label-medium-line-height) var(--mat-sys-label-medium-font);\n';
  css += '  --mat-sys-label-medium-font: var(--mat-sys-plain-font-family);\n';
  css += '  --mat-sys-label-medium-line-height: 1rem;\n';
  css += '  --mat-sys-label-medium-size: 0.75rem;\n';
  css += '  --mat-sys-label-medium-tracking: 0.031rem;\n';
  css += '  --mat-sys-label-medium-weight: var(--mat-sys-medium-font-weight);\n';
  css += '  --mat-sys-label-medium-weight-prominent: var(--mat-sys-bold-font-weight);\n';

  // Label small typescale system variables
  css += '\n  /* Label small typescale */\n';
  css +=
    '  --mat-sys-label-small: var(--mat-sys-label-small-weight) var(--mat-sys-label-small-size) / var(--mat-sys-label-small-line-height) var(--mat-sys-label-small-font);\n';
  css += '  --mat-sys-label-small-font: var(--mat-sys-plain-font-family);\n';
  css += '  --mat-sys-label-small-line-height: 1rem;\n';
  css += '  --mat-sys-label-small-size: 0.688rem;\n';
  css += '  --mat-sys-label-small-tracking: 0.031rem;\n';
  css += '  --mat-sys-label-small-weight: var(--mat-sys-medium-font-weight);\n';

  // Title large typescale system variables
  css += '\n  /* Title large typescale */\n';
  css +=
    '  --mat-sys-title-large: var(--mat-sys-title-large-weight) var(--mat-sys-title-large-size) / var(--mat-sys-title-large-line-height) var(--mat-sys-title-large-font);\n';
  css += '  --mat-sys-title-large-font: var(--mat-sys-brand-font-family);\n';
  css += '  --mat-sys-title-large-line-height: 1.75rem;\n';
  css += '  --mat-sys-title-large-size: 1.375rem;\n';
  css += '  --mat-sys-title-large-tracking: 0;\n';
  css += '  --mat-sys-title-large-weight: var(--mat-sys-regular-font-weight);\n';

  // Title medium typescale system variables
  css += '\n  /* Title medium typescale */\n';
  css +=
    '  --mat-sys-title-medium: var(--mat-sys-title-medium-weight) var(--mat-sys-title-medium-size) / var(--mat-sys-title-medium-line-height) var(--mat-sys-title-medium-font);\n';
  css += '  --mat-sys-title-medium-font: var(--mat-sys-plain-font-family);\n';
  css += '  --mat-sys-title-medium-line-height: 1.5rem;\n';
  css += '  --mat-sys-title-medium-size: 1rem;\n';
  css += '  --mat-sys-title-medium-tracking: 0.009rem;\n';
  css += '  --mat-sys-title-medium-weight: var(--mat-sys-medium-font-weight);\n';

  // Title small typescale system variables
  css += '\n  /* Title small typescale */\n';
  css +=
    '  --mat-sys-title-small: var(--mat-sys-title-small-weight) var(--mat-sys-title-small-size) / var(--mat-sys-title-small-line-height) var(--mat-sys-title-small-font);\n';
  css += '  --mat-sys-title-small-font: var(--mat-sys-plain-font-family);\n';
  css += '  --mat-sys-title-small-line-height: 1.25rem;\n';
  css += '  --mat-sys-title-small-size: 0.875rem;\n';
  css += '  --mat-sys-title-small-tracking: 0.006rem;\n';
  css += '  --mat-sys-title-small-weight: var(--mat-sys-medium-font-weight);\n';

  return css;
}

/**
 * Gets CSS for elevation system variables.
 * @returns String with CSS for elevation system variables.
 */
function getElevationSysVariablesCSS(): string {
  let css = '';

  css += '\n  /* Box shadow colors. Only used in the elevation level system variables. */\n';
  css += '  --mat-sys-umbra-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 80%);\n';
  css +=
    '  --mat-sys-penumbra-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 86%);\n';
  css += '  --mat-sys-ambient-color: color-mix(in srgb, var(--mat-sys-shadow), transparent 88%);\n';

  css +=
    '\n  /* Elevation level system variables. These are used as the value for box-shadow CSS property. */\n';
  css +=
    '  --mat-sys-level0: 0px 0px 0px 0px var(--mat-sys-umbra-color), 0px 0px 0px 0px var(--mat-sys-penumbra-color), 0px 0px 0px 0px var(--mat-sys-ambient-color);\n';
  css +=
    '  --mat-sys-level1: 0px 2px 1px -1px var(--mat-sys-umbra-color), 0px 1px 1px 0px var(--mat-sys-penumbra-color), 0px 1px 3px 0px var(--mat-sys-ambient-color);\n';
  css +=
    '  --mat-sys-level2: 0px 3px 3px -2px var(--mat-sys-umbra-color), 0px 3px 4px 0px var(--mat-sys-penumbra-color), 0px 1px 8px 0px var(--mat-sys-ambient-color);\n';
  css +=
    '  --mat-sys-level3: 0px 3px 5px -1px var(--mat-sys-umbra-color), 0px 6px 10px 0px var(--mat-sys-penumbra-color), 0px 1px 18px 0px var(--mat-sys-ambient-color);\n';
  css +=
    '  --mat-sys-level4: 0px 5px 5px -3px var(--mat-sys-umbra-color), 0px 8px 10px 1px var(--mat-sys-penumbra-color), 0px 3px 14px 2px var(--mat-sys-ambient-color);\n';
  css +=
    '  --mat-sys-level5: 0px 7px 8px -4px var(--mat-sys-umbra-color), 0px 12px 17px 2px var(--mat-sys-penumbra-color), 0px 5px 22px 4px var(--mat-sys-ambient-color);\n';

  return css;
}

/**
 * Gets CSS for shape system variables.
 * @returns String with CSS for shape system variables.
 */
function getShapeSysVariablesCSS(): string {
  let css = '';
  css += '  --mat-sys-corner-extra-large: 28px;\n';
  css += '  --mat-sys-corner-extra-large-top: 28px 28px 0 0;\n';
  css += '  --mat-sys-corner-extra-small: 4px;\n';
  css += '  --mat-sys-corner-extra-small-top: 4px 4px 0 0;\n';
  css += '  --mat-sys-corner-full: 9999px;\n';
  css += '  --mat-sys-corner-large: 16px;\n';
  css += '  --mat-sys-corner-large-end: 0 16px 16px 0;\n';
  css += '  --mat-sys-corner-large-start: 16px 0 0 16px;\n';
  css += '  --mat-sys-corner-large-top: 16px 16px 0 0;\n';
  css += '  --mat-sys-corner-medium: 12px;\n';
  css += '  --mat-sys-corner-none: 0;\n';
  css += '  --mat-sys-corner-small: 8px;\n';
  return css;
}

/**
 * Gets CSS for state system variables.
 * @returns String with CSS for state system variables.
 */
function getStateSysVariablesCSS(): string {
  let css = '';
  css += '  --mat-sys-dragged-state-layer-opacity: 0.16;\n';
  css += '  --mat-sys-focus-state-layer-opacity: 0.12;\n';
  css += '  --mat-sys-hover-state-layer-opacity: 0.08;\n';
  css += '  --mat-sys-pressed-state-layer-opacity: 0.12;\n';
  return css;
}

/**
 * Gets CSS for all system variables.
 * @param lightColorScheme Dynamic material scheme for a light high contrast theme which contains the color role values.
 * @param darkColorScheme Dynamic material scheme for a dark high contrast theme which contains the color role values.
 * @returns String with CSS for all system variables.
 */
export function getAllSysVariablesCSS(
  lightColorScheme: DynamicScheme,
  darkColorScheme: DynamicScheme,
): string {
  let css = '';

  css += '  /* COLOR SYSTEM VARIABLES */\n';
  css += '  color-scheme: light;\n\n';
  css += getColorSysVariablesCSS(lightColorScheme, darkColorScheme);

  css += '\n  /* TYPOGRAPHY SYSTEM VARIABLES */\n';
  css += getTypographySysVariablesCSS();

  css += '\n  /* ELEVATION SYSTEM VARIABLES */\n';
  css += getElevationSysVariablesCSS();

  css += '\n  /* SHAPE SYSTEM VARIABLES */\n';
  css += getShapeSysVariablesCSS();

  css += '\n  /* STATE SYSTEM VARIABLES */\n';
  css += getStateSysVariablesCSS();

  return css;
}

/**
 * Gets CSS for high contrast color values to be automatically used when users specify.
 * @param lightColorScheme Dynamic material scheme for a light high contrast theme which contains the color role values.
 * @param darkColorScheme Dynamic material scheme for a dark high contrast theme which contains the color role values.
 * @returns String with CSS for high contrast media query and values.
 */
export function getHighContrastOverridesCSS(
  lightColorScheme: DynamicScheme,
  darkColorScheme: DynamicScheme,
): string {
  let css = '\n';
  css += '  @media (prefers-contrast: more) {\n';
  css += getColorSysVariablesCSS(lightColorScheme, darkColorScheme, /* isHighContrast */ true);
  css += '  }\n';
  return css;
}

/**
 * Creates theme file for provided scss.
 * @param content String content for the theme file.
 * @param tree Directory tree.
 * @param directory Path for generated file.
 * @param isScss Whether or not the generated file is a scss file. If false will output a CSS file.
 * @param directory Directory path to place generated theme file.
 */
function createThemeFile(content: string, tree: Tree, directory?: string, isScss: boolean = true) {
  const fileName = isScss ? '_theme-colors.scss' : 'theme.css';
  const filePath = directory ? directory + fileName : fileName;
  tree.create(filePath, content);
}

/**
 * Gets the comment that lists the color customizations specified from the schematic inputs.
 * @param primaryColor Hex color that represents the primary palette.
 * @param secondaryColor Hex color that represents the secondary palette.
 * @param tertiaryColor Hex color that represents the tertiary palette.
 * @param neutralColor Hex color that represents the neutral palette.
 * @returns String with specified color customizations
 */
function getColorComment(
  primaryColor: string,
  secondaryColor?: string,
  tertiaryColor?: string,
  neutralColor?: string,
  neutralVariantColor?: string,
  errorColor?: string,
) {
  let colorComment = 'Color palettes are generated from primary: ' + primaryColor;
  if (secondaryColor) {
    colorComment += ', secondary: ' + secondaryColor;
  }
  if (tertiaryColor) {
    colorComment += ', tertiary: ' + tertiaryColor;
  }
  if (neutralColor) {
    colorComment += ', neutral: ' + neutralColor;
  }
  if (neutralVariantColor) {
    colorComment += ', neutral variant: ' + neutralVariantColor;
  }
  if (errorColor) {
    colorComment += ', error: ' + errorColor;
  }
  return colorComment;
}

export default function (options: Schema): Rule {
  return async (tree: Tree, context: SchematicContext) => {
    const colorComment = getColorComment(
      options.primaryColor,
      options.secondaryColor,
      options.tertiaryColor,
      options.neutralColor,
      options.neutralVariantColor,
      options.errorColor,
    );

    const colorPalettes = getColorPalettes(
      options.primaryColor,
      options.secondaryColor,
      options.tertiaryColor,
      options.neutralColor,
      options.neutralVariantColor,
      options.errorColor,
    );

    let lightHighContrastColorScheme: DynamicScheme;
    let darkHighContrastColorScheme: DynamicScheme;
    if (options.includeHighContrast) {
      lightHighContrastColorScheme = getMaterialDynamicScheme(
        colorPalettes.primary,
        colorPalettes.secondary,
        colorPalettes.tertiary,
        colorPalettes.neutral,
        colorPalettes.neutralVariant,
        /* isDark */ false,
        /* contrastLevel */ 1.0,
      );

      darkHighContrastColorScheme = getMaterialDynamicScheme(
        colorPalettes.primary,
        colorPalettes.secondary,
        colorPalettes.tertiary,
        colorPalettes.neutral,
        colorPalettes.neutralVariant,
        /* isDark */ true,
        /* contrastLevel */ 1.0,
      );

      // Error palettes get generated by the color scheme's other palettes. Override the generated
      // error palette with the custom one if applicable.
      if (options.errorColor) {
        lightHighContrastColorScheme.errorPalette = colorPalettes.error;
        darkHighContrastColorScheme.errorPalette = colorPalettes.error;
      }
    }

    if (options.isScss) {
      let themeScss = generateSCSSTheme(colorPalettes, colorComment);

      // Add high contrast overrides mixins to generated file if specified
      if (options.includeHighContrast) {
        themeScss += generateHighContrastOverrideMixinsSCSS(
          lightHighContrastColorScheme!,
          darkHighContrastColorScheme!,
        );
      }

      createThemeFile(themeScss, tree, options.directory);
    } else {
      let themeCss = '';
      themeCss += '/* Note: ' + colorComment + ' */\n';
      themeCss += 'html {\n';

      const lightColorScheme = getMaterialDynamicScheme(
        colorPalettes.primary,
        colorPalettes.secondary,
        colorPalettes.tertiary,
        colorPalettes.neutral,
        colorPalettes.neutralVariant,
        /* isDark */ false,
        /* contrastLevel */ 0,
      );

      const darkColorScheme = getMaterialDynamicScheme(
        colorPalettes.primary,
        colorPalettes.secondary,
        colorPalettes.tertiary,
        colorPalettes.neutral,
        colorPalettes.neutralVariant,
        /* isDark */ true,
        /* contrastLevel */ 0,
      );

      // Error palettes get generated by the color scheme's other palettes. Override the generated
      // error palette with the custom one if applicable.
      if (options.errorColor) {
        lightColorScheme.errorPalette = colorPalettes.error;
        darkColorScheme.errorPalette = colorPalettes.error;
      }

      themeCss += getAllSysVariablesCSS(lightColorScheme, darkColorScheme);

      //  Add high contrast media query to overwrite the color values when the user specifies
      if (options.includeHighContrast) {
        themeCss += getHighContrastOverridesCSS(
          lightHighContrastColorScheme!,
          darkHighContrastColorScheme!,
        );
      }

      themeCss += '}\n';
      createThemeFile(themeCss, tree, options.directory, /* isScss */ false);
    }
  };
}
